/*
Responsible for the loading and playing of Frame animations
 */

// This is a great idea for making a Processing Contributed Library

class Animation {

  String name;
  PImage[] images;
  int number_of_frames;

  boolean running = true;
  int frame=0; 

  float x_loc;
  float y_loc;

  Animation(String name, int number_of_frames, float x_loc, float y_loc) {
    this.name = name;
    this.number_of_frames = number_of_frames;

    images = new PImage[number_of_frames];
    for(int x=0;x < number_of_frames; x++) {
      images[x] = loadImage( "data/"+name+"/"+(x+1)+".png");
    }

    this.x_loc = x_loc;
    this.y_loc = y_loc;
  }

  void play() {
    if(running) {
      image(images[frame],x_loc,y_loc);
      frame++;
      if(frame > number_of_frames-1) {
        running = false;
      }
    }
    else {
      image(images[number_of_frames-1],x_loc,y_loc);
    }
  }
}

/* Calculates the collision state of two objects
 can be used to link two objects or reused to test multiple different objects
 */


class Collision_detector {

  boolean collision;

  Particle one;
  Particle two;

  float distance;

  float[] location = new float[2];

  Collision_detector() {
  } 

  void set_particle_one(Particle one) {
    this.one = one;
  }


  void set_particle_two(Particle two ) {
    this.two = two;
  }

  float get_distance() {
    collision_distance();
    return distance;
  }


  boolean is_collision(Particle two) {
    this.two = two; 
    return is_collision();
  }

  boolean is_collision(Particle one, Particle two) {
    this.one = one;
    this.two = two;
    return is_collision();
  }


  boolean is_collision() {
    collision_distance();
    collision = (distance < one.radius+two.radius)?true:false;
    distance = -1;
    return collision;
  }

  float[] get_location() {
    ////// calculate the point of impact of the last collision
    return location;
  }

  void collision_distance() {
    distance = sqrt(  sq(two.x_loc - one.x_loc) + sq(two.y_loc - one.y_loc));
  }
}

/*
Various useful math functions.
*/


float repulse(float o, float x1, float y1, float x2, float y2, float change) {

  float dp = direction_to(x1, y1, x2, y2);
  if(dp > o) {
    if( dp - o < 360 -dp + o) {
      return change*-1;
    }
    else {
      return change;
    }
  }

  if(dp < o) {
    if(  o - dp < 360-o+dp ) {
      return change;
    }
    else {
      return change*-1;
    }
  }
  return 0;
}


float distance(float x1,float y1, float x2, float y2) {
  return sqrt(  sq(x2 - x1) + sq(y2 - y1));
}

float direction_to(float one_x_loc, float one_y_loc, float two_x_loc, float two_y_loc) {
  float a = degrees(atan2(one_y_loc - two_y_loc, one_x_loc- two_x_loc)) +180 ;
  return a;
}


float attract(float o, float x1, float y1, float x2, float y2, float change) {


  float dp = direction_to(x1, y1, x2, y2);
    if(dp > o) {
    if( dp - o < 360 -dp + o) {
      return change;
    }
    else {
      return change*-1;
    }
  }

  if(dp < o) {
    if(  o - dp < 360-o+dp ) {
      return change*-1;
    }
    else {
      return change;
    }
  }
  return 0;
}
/*
Applys the rotational pull between every Particle.
*/

class Gravity {

  Particle one;
  Particle two;

  float gravity;
  float strength;

 
  float distance;

  Gravity() {
  } 

  void set_particle_one(Particle one) {
    this.one = one;
  }

  void set_particle_two(Particle two) {
    this.two = two;
  }

  void set_distance(float distance) {
    this.distance = distance;
  }

  void apply_gravity(Particle two) {
    this.two = two; 
    apply_gravity();
  }

  void apply_gravity(Particle one, Particle two) {
    this.one = one;
    this.two = two;
    apply_gravity();
  }

  void apply_gravity() {
    calculate_strength();
    apply_rotation();
  }

  /// dump all saved data
  void reset() {
    one = null;
    two  = null;
    strength = 0;
    gravity = 0;
  }

  void apply_rotation() {
    one.rotation = one.rotation + attract(one.rotation,one.x_loc,one.y_loc,two.x_loc,two.y_loc,strength);
  }



  float calculate_strength() {
    strength = ((distance*distance)*(one.mass+two.mass))*.001;
   
    return strength;
  }

  float attract(float o, float x1, float y1, float x2, float y2, float change) {
   
    float direction_of = direction_to(x1, y1, x2, y2);
   
    if(direction_of > o) {
      if( direction_of - o < 360 -direction_of + o) {
        return change;
      }
      else {
        return change*-1;
      }
    }
    if(direction_of < o) {
      if(  o - direction_of < 360-o+direction_of ) {
        return change*-1;
      }
      else {
        return change;
      }
    }  
    return 0;

  }

  float direction_to(float one_x_loc, float one_y_loc, float two_x_loc, float two_y_loc) {
    float a = degrees(atan2(one_y_loc - two_y_loc, one_x_loc- two_x_loc)) +180 ;
    return a;
   
  }
  
  
  
  
}

/*
A group of Particles.
*/

class Particle_group {

  int number_of_particles; 

  Particle[] particles;

  ArrayList dead_list;


  System system;

  Particle_group(int number_of_particles) {
    particles  = new Particle[number_of_particles];
    dead_list = new ArrayList();

    this.number_of_particles = number_of_particles;

    for(int x=0;x<number_of_particles;x++) {
      particles[x] = new Particle();
    }
  }

  void render() {
    for(int x=0;x<number_of_particles;x++) {
      particles[x].render();
    }
  }

  void math() {
    for(int x=0;x<number_of_particles;x++) {
      particles[x].math();
    }
  }

  boolean kill_particle(Particle particle) {
    if(particle.get_alive()) {
      particle.set_alive(false);
      dead_list.add(particle);
      return true;
    }
    else {
     // println(" ::dead things cant die:: ERROR:PARTICLE_GROUP.KILL_PARTICLE ");
      return false;
    }
  }

  Particle live_particle(float x_loc, float y_loc) {
    Particle particle;

    particle = (Particle)dead_list.get(0);
    dead_list.remove(0);

    particle.reset(x_loc,y_loc);
    particle.set_alive(true);
    return particle;
  }

  int dead_list_size() {
    return dead_list.size();
  }
}

/*
The base class for objects affected by Collision and Gravity.
*/

class Particle {

  float x_loc; 
  float y_loc;


  int mass; ///diameter
  float radius;

  float rotation;
  float velocity;

  boolean alive=true;

  boolean star=false;
  PImage star_image;
  color cc;

 Particle() {
    float offset=0;
    set_mass(1);

    cc =  color(random(200,255),random(200,255),random(200,255));

    x_loc = random(200,width);
    y_loc = random(0,height);
    rotation = random(0,360);
    star_image = loadImage("data/star.png");
    velocity = .5;
  }


  void set_x() {
    x_loc = x_loc + cos(radians(rotation))*velocity ;
  }
  void set_y() {
    y_loc = y_loc + sin(radians(rotation))*velocity ;
  }


  void math() {
    if(alive) {

      clamp_to_stage();
      clamp_rotation();

      speed();

      set_x();
      set_y();
    }
  }

  ///// never change mass any other way or it will explode in bad ways !
  void set_mass(int mass) {
    this.mass = mass;
    radius = (float)mass/2;
  }


  void speed() {
    if(mass <=4) {
      velocity = .5;
      star=false;
    }

    if ((mass >4) && (mass<10)) {
      velocity = .2;
      star = true;
    }
    else {
      if(mass>10) {
        velocity =.1;
        //space whale = true; its reals
      }
    }
  }

  void render() {
    if(alive) {
      if(star) {
        pushMatrix();
        image(star_image,x_loc,y_loc,mass+1,mass+1);
        popMatrix();
      }
      else {
        fill(cc);
        rect(x_loc,y_loc,mass,mass);
      }
    }
  }

  void set_alive(boolean alive) {
    this.alive = alive;
  }

  boolean get_alive() {
    return alive;
  }

int stage_offset=5;
 
  void clamp_to_stage() {
    if(x_loc<stage_offset) {
      x_loc =0+velocity+stage_offset;
      rotation = rotation+(180-(rotation*2));
    }
    else {
      if(x_loc>width-stage_offset) {
        x_loc =width-velocity-stage_offset;
        rotation = rotation+(180-(rotation*2));
      }
    }

    if(y_loc<stage_offset) {
      y_loc =0+velocity+stage_offset;
      rotation = rotation-(180-(rotation*2));
    }
    else {
      if(y_loc>height-stage_offset) {
        y_loc =height-velocity-stage_offset;
        rotation = rotation-(180-(rotation*2));
      }
    }
  }



  void clamp_rotation() {
    if(rotation > 360) {
      rotation = rotation - 360;
    } 
    if(rotation < 0) {
      rotation = rotation + 360;
    }
  }

  void reset(float x_loc, float y_loc) {
    set_mass(1);
    velocity = .3;
    rotation = random(0,360);
    this.x_loc = x_loc;
    this.y_loc = y_loc;
  }
}

/*
Needed for JavaScript Version.
Directive to ps.js compiler to pre load images.
*/
 /* @pjs preload="
   data/sunPhysics_4_blackBackground.png,
   data/star.png,
   data/ship.png,
   data/ship1.png,
   data/leftEye/1.png,
   data/leftEye/2.png,
   data/leftEye/3.png,
   data/leftEye/4.png,
   data/leftEye/5.png,
   data/leftEye/6.png,
   data/leftEye/7.png,
   data/leftEye/8.png,
   data/leftEye/9.png,
   data/leftEye/10.png,
   data/leftEye/11.png,
   data/leftEye/12.png,
   data/leftEye/13.png,
   data/leftEye/14.png,
   data/leftEye/15.png,
   data/leftEye/16.png,
   data/leftEye/17.png,
   data/leftEye/18.png,
   data/leftEye/19.png,
   data/rightEye/1.png,
   data/rightEye/2.png,
   data/rightEye/3.png,
   data/rightEye/4.png,
   data/rightEye/5.png,
   data/rightEye/6.png,
   data/rightEye/7.png,
   data/rightEye/8.png,
   data/rightEye/9.png,
   data/rightEye/10.png,
   data/rightEye/11.png,
   data/rightEye/12.png,
   data/rightEye/13.png,
   data/rightEye/14.png,
   data/rightEye/15.png,
   data/rightEye/16.png,
   data/rightEye/17.png,
   data/rightEye/18.png,
   data/rightEye/19.png
   "; 
   */
////////// the land of the flying raptor and the home of the eye ball ship animation

class Space_ship extends Particle {

  Gravity gravity;

  Collision_detector collision_detector_left;
  Collision_detector collision_detector_right;

  Animation animation_left_eye;
  Animation animation_right_eye;

  Cursor my_cursor;

  Right_eye right_eye;
  Left_eye left_eye;


  Space_ship() {  
    super();

    velocity = 1;
    set_mass(10);

    stage_offset = 12;

    x_loc = 574;
    y_loc = 78;

    my_cursor    = new Cursor();

    left_eye     = new Left_eye(this,75,60,10,10);
    right_eye    = new Right_eye(this,260,120,10,10);//38

    animation_left_eye  = new Animation("leftEye",19,91,54);
    animation_right_eye = new Animation("rightEye",19,274,115);

    gravity = new Slopy_hack();
    collision_detector_left = new Collision_detector();
    collision_detector_right = new Collision_detector();

    collision_detector_left.set_particle_one(this);
    collision_detector_left.set_particle_two(left_eye);

    collision_detector_right.set_particle_one(this);
    collision_detector_right.set_particle_two(right_eye);


    gravity.set_particle_one(this);
    gravity.set_particle_two(my_cursor);

    ship_RENDER_setup();
  }

  boolean collision;

  void math() {


    if((! collision_detector_right.collision) && (!collision_detector_left.collision)) {

      my_cursor.math();

      right_eye.math();
      left_eye.math();

      right_eye.set_collision(collision_detector_right.is_collision());
      left_eye.set_collision(collision_detector_left.is_collision());


      gravity.set_distance(0);
      gravity.apply_gravity();



      super.math();
    }
  }


  void render() {
    if((! collision_detector_right.collision) && (!collision_detector_left.collision)) {

      fill(255,0,0);
      ellipse(x_loc,y_loc,mass,mass);
      ship_RENDER();
    }
    else {
    }

    left_eye.render();
    right_eye.render();
  }



  class Right_eye extends Eye {

    Right_eye(Particle ship,float x_loc, float y_loc, int mass, float dis) {
      super(ship,x_loc,y_loc,mass,dis);
    } 

    void render() {
      if (collision) {
        animation_right_eye.play();
      }
      else {
        fill(100,100,150);
        ellipse(x_loc, y_loc, mass, mass);
      }
    }
  }

  class Left_eye extends Eye {

    Left_eye(Particle ship,float x_loc, float y_loc, int mass, float dis) {
      super(ship,x_loc,y_loc,mass,dis);
    }

    void render() {
      if (collision) {
        animation_left_eye.play();
      }
      else {
        fill(100,100,150);
        ellipse(x_loc, y_loc, mass, mass);
      }
    }
  }



  class Cursor extends Particle {

    Cursor() {
      super();
      set_mass(5);
      velocity = 0;
      x_loc = width;
      y_loc = height;
    }

    void math() {
      x_loc = mouseX;
      y_loc = mouseY;
    }
  }

  class Slopy_hack extends Gravity {

    Slopy_hack() {
    }

    float calculate_strength() {
      strength = 10;

      return strength;
    }
  }

  PImage ship;
  PImage ship1;


  void ship_RENDER_setup() {
    ship =loadImage("data/ship.png");
    ship1=loadImage("data/ship1.png");
  }


  boolean pass;

  void ship_RENDER() {
    // noFill();
    // ellipseMode(RADIUS);
    //ellipse(x_locOne,y_locOne,r1,r1);
    // fill(255);


    imageMode(CENTER);
    if(pass) {
      image(ship,x_loc,y_loc);
      pass = false;
    }
    else {
      image(ship1,x_loc,y_loc);
      pass = true;
    }
  }
}
class Eye extends Particle {

  boolean collision;

  float dis;
  float center_x;
  float center_y;
  Particle ship;

  Eye(Particle ship,float x_loc, float y_loc, int mass, float dis) {
    center_x=x_loc;
    center_y=y_loc;
    set_mass(mass);
    this.dis = dis;
    this.ship = ship;
  }

  void set_collision(boolean collision) {
    this.collision = collision;
  }



  void math() {
    rotation = direction_to(ship.x_loc,ship.y_loc,center_x,center_y)-180;
    x_loc= center_x  +  cos(radians(rotation))*dis;
    y_loc= center_y  +  sin(radians(rotation))*dis;
  }
}

///// not even flying raptors can defeat HACK A SOAReS` REX!

/*
Generated background of colored stars.
*/
class Star_field {

  int number_of_stars;  
  Star[] stars;

  Star_field(int number_of_stars) {
    this.number_of_stars = number_of_stars;
    stars = new Star[number_of_stars]; 
    for(int x=0; x< number_of_stars; x++) {
      stars[x] = new Star();
    }
  }

  void math() {
    for(int x=0; x< number_of_stars; x++) {
      stars[x].math();
    }
  }


  void render() {
    for(int x=0; x< number_of_stars; x++) {
      stars[x].render();
    }
  }



  class Star {
    float x_loc;
    float y_loc;

    float velocity;
    float radius;
    color cc;

    Star() {
      this.cc = color(random(50,100),random(50,100),random(50,100));
      this.velocity = random(.3,.6);
      this.radius   = random(1,1.8);
      x_loc = random(0,width);
      y_loc = random(0,height);
    }

    void math() {

      if(x_loc < -5) {
        x_loc = width+5;
      }

      x_loc = x_loc - velocity;
    }

    void render() {
      fill(cc);
      rect(x_loc,y_loc,radius,radius);
    }
  }
}

System system;
Particle_group particle_group;
Star_field star_field;

PImage foreground;
Space_ship space_ship;

void setup() {
  size(800,200);

  background(0);

  particle_group = new Particle_group(80);
  system = new System(particle_group);

  star_field = new Star_field(200);

  foreground = loadImage("data/sunPhysics_4_blackBackground.png");

  space_ship = new Space_ship();

  smooth();
  noStroke();

  rectMode(CENTER);
  shapeMode(CENTER);
  imageMode(CENTER);

  frameRate(30);
}

void draw() {



  fill(0,0,0,100);
  rect(0+width/2,0+height/2,width,height);

  system.math();
  particle_group.math();
  star_field.math();
  space_ship.math();

  particle_group.render();
  star_field.render();

  image(foreground,width/2,height/2);
  space_ship.render();
}

/*
Skecth engine applys Gravity and takes action on Collisions.
*/

class System {

  Particle_group particle_group;
  Particle[] particles;

  Collision_detector collision_detector;
  Gravity gravity;

  int number_of_particles;


  System(Particle_group particle_group) {
    this.particle_group = particle_group;

    particles = particle_group.particles;
    number_of_particles = particles.length;

    collision_detector = new Collision_detector();
    gravity = new Gravity();
  }

  void math() {

    for(int active=0; active<number_of_particles;active++) {
      for(int passive=0;passive<number_of_particles;passive++) {

        collision_detector.set_particle_one(particles[active]);
        gravity.set_particle_one(particles[active]);

        if((active !=passive) && (particles[passive].alive) && (particles[active].alive)) {         

          if(collision_detector.is_collision(particles[passive])) { 
            if(particles[active].mass >= particles[passive].mass) {

              explode(particles[active],particles[passive]);
              merge(particles[active],particles[passive]);
            }
          }
          else {
            gravity.set_distance(collision_detector.distance);
            gravity.apply_gravity(particles[passive]);
          }
        }
      }
    }
  }


  void explode(Particle one,Particle two) {
    if( ( one.mass>two.mass) && (one.mass>=6)  ) {
      one.set_mass(one.mass-1);
      particle_group.live_particle(two.x_loc,two.y_loc);
      particle_group.kill_particle(two);

      for(int x=0; x < two.mass ; x++) {
        particle_group.live_particle(one.x_loc,one.y_loc);
      }
    }
  }

  void merge(Particle one,Particle two) {
    if(two.mass <6) {
      one.set_mass(one.mass+1);
      particle_group.kill_particle(two);
    }
  }
}

